package com.sunshine.geek.bytecode;
import com.sunshine.geek.bytecode.struct.attribute.Attribute;
import com.sunshine.geek.bytecode.struct.attribute.CodeAttribute;
import com.sunshine.geek.bytecode.struct.attribute.ExceptionAttribute;
import com.sunshine.geek.bytecode.struct.attribute.ExceptionIndexTable;
import com.sunshine.geek.bytecode.struct.attribute.ExceptionTable;
import com.sunshine.geek.bytecode.struct.attribute.FieldAttributre;
import com.sunshine.geek.bytecode.struct.attribute.LineNumberTaleAttribue;
import com.sunshine.geek.bytecode.struct.attribute.LocalVariableTableAttribute;
import com.sunshine.geek.bytecode.struct.attribute.SourceFileAttribute;
import com.sunshine.geek.bytecode.struct.attribute.StackMapFrame;
import com.sunshine.geek.bytecode.struct.attribute.StackMapTable;
import com.sunshine.geek.bytecode.struct.info.ConstDoubleInfo;
import com.sunshine.geek.bytecode.struct.info.ConstFieldRefInfo;
import com.sunshine.geek.bytecode.struct.info.ConstFloatInfo;
import com.sunshine.geek.bytecode.struct.info.ConstIntegerInfo;
import com.sunshine.geek.bytecode.struct.info.ConstInterfaceMethodRefInfo;
import com.sunshine.geek.bytecode.struct.info.ConstInvokeDynamicInfo;
import com.sunshine.geek.bytecode.struct.info.ConstLongInfo;
import com.sunshine.geek.bytecode.struct.info.ConstMethodHandleInfo;
import com.sunshine.geek.bytecode.struct.info.ConstMethodRefInfo;
import com.sunshine.geek.bytecode.struct.info.ConstMethodTypeInfo;
import com.sunshine.geek.bytecode.struct.info.ConstNameAndTypeInfo;
import com.sunshine.geek.bytecode.struct.info.ConstPoolType;
import com.sunshine.geek.bytecode.struct.info.ConstStringInfo;
import com.sunshine.geek.bytecode.struct.info.ConstUtf8Info;
import com.sunshine.geek.bytecode.struct.info.ConstsClassInfo;
import com.sunshine.geek.bytecode.struct.info.FieldInfo;
import com.sunshine.geek.bytecode.struct.info.LargeNumberContinue;
import com.sunshine.geek.bytecode.struct.info.LineNumberTableInfo;
import com.sunshine.geek.bytecode.struct.info.LocalVariableInfo;
import com.sunshine.geek.bytecode.struct.info.MethodInfo;
import com.sunshine.geek.bytecode.tools.Bytes;

public class ClassFileParse {
	private int index = 0;
	private byte[] bytes = null;

	public ClassFileParse() {
	
	}
	
	public void setBytes(byte[] classByte){
		int len = classByte.length;
		bytes = new byte[len];
		System.arraycopy(classByte, 0, bytes, 0, len);
		index = 0;
	}
	public ClassFileParse(byte[] classByte) {
		int len = classByte.length;
		bytes = new byte[len];
		System.arraycopy(classByte, 0, bytes, 0, len);
		index = 0;
	}

	public ClassInfo parseClassByte() {
		ClassInfo classInfo = new ClassInfo();
		
		/**
		 * 閿熸枻鎷烽敓鏂ゆ嫹榄旈敓鏂ゆ嫹閿熸枻鎷�
		 */
		classInfo.setMagicNumber(Bytes.u4(readByte(4)));
		classInfo.setMinorVersion(Bytes.u2(readByte(2)));
		classInfo.setMajorVersion(Bytes.u2(readByte(2)));
		classInfo.setConstPoolSize(Bytes.u2(readByte(2)));//
		parseConstPool(classInfo);
		classInfo.setAccessFlags(Bytes.u2(readByte(2)));
		classInfo.setThisClass(Bytes.u2(readByte(2)));
		classInfo.setSuperClass(Bytes.u2(readByte(2)));
		classInfo.setInterfacesCount(Bytes.u2(readByte(2)));
		//閿熸枻鎷烽敓鏂ゆ嫹 interfaces
		for(int i=0;i<classInfo.getInterfacesCount();i++){
			classInfo.getInterfaces().add(Integer.valueOf(Bytes.u2(readByte(2))));
		}
		classInfo.setFieldCount(Bytes.u2(readByte(2)));
		for(int i=0;i<classInfo.getFieldCount();i++){
			//閿熸枻鎷烽敓鏂ゆ嫹閿熻璁规嫹閿熸枻鎷烽敓鏂ゆ嫹
			parseFieldInfo(classInfo);
		}
		
		classInfo.setMethodCount(Bytes.u2(readByte(2)));
		for(int i=0;i<classInfo.getMethodCount();i++){
			parseMethodInfo(classInfo);
		}
		classInfo.setAttributesCount(Bytes.u2(readByte(2)));
		for(int i=0;i<classInfo.getAttributesCount();i++){
			int attributeNameIndex = Bytes.u2(readByte(2));
			classInfo.getAttribute().add(parseAttribute(attributeNameIndex, classInfo));
		}
		
		return classInfo;
	}

	
	private void parseMethodInfo(ClassInfo classInfo) {
		MethodInfo methodInfo = new MethodInfo();
		methodInfo.setAccessFlags(Bytes.u2(readByte(2)));
		methodInfo.setNameIndex(Bytes.u2(readByte(2)));
		methodInfo.setDescriptorIndex(Bytes.u2(readByte(2)));
		methodInfo.setAttributeCount(Bytes.u2(readByte(2)));
		for(int i=0;i<methodInfo.getAttributeCount();i++){
			int attributeNameIndex =Bytes.u2(readByte(2));
			methodInfo.getMethodAttribute().add(parseAttribute(attributeNameIndex, classInfo));
		}
		classInfo.getMethodInfo().add(methodInfo);
		
		//System.out.println(methodInfo);
	}

	private void parseFieldInfo(ClassInfo classInfo) {
		FieldInfo field = new FieldInfo();
		field.setAccessFlag(Bytes.u2(readByte(2)));
		field.setNameIndex(Bytes.u2(readByte(2)));
		field.setDescriptorIndex(Bytes.u2(readByte(2)));
		field.setAttributeCount(Bytes.u2(readByte(2)));
		for(int i=0;i<field.getAttributeCount();i++){
			FieldAttributre attribute = new FieldAttributre();
			attribute.setAttributeNameIndex(Bytes.u2(readByte(2)));
			attribute.setAttributeLength(Bytes.u4(readByte(4)));
			attribute.setInfo(Bytes.u2(readByte(2)));
			field.getAttributes().add(attribute);
		}
		classInfo.getFieldInfo().add(field);
	}

	public Attribute parseAttribute(int attributeNameIndex, ClassInfo classInfo) {
		ConstUtf8Info utf8Info = (ConstUtf8Info) classInfo.getConstPools().get(
				attributeNameIndex - 1);
		String type = utf8Info.getContent();
		if (type.equals("Code")) {

			CodeAttribute ma = new CodeAttribute();
			ma.setAttributeNameIndex(attributeNameIndex);
			ma.setAttributeLength(Bytes.u4(readByte(4)));
			ma.setMaxStack(Bytes.u2(readByte(2)));
			ma.setMaxLocals(Bytes.u2(readByte(2)));
			ma.setCodeLenght(Bytes.u4(readByte(4)));

			for (int j = 0; j < ma.getCodeLenght(); j++) {
				ma.getCodes().add(Integer.valueOf(Bytes.u1(readByte(1)[0])));
			}
			// end for code
			// begin exception Table
			ma.setExceptionTableLength(Bytes.u2(readByte(2)));
			for (int j = 0; j < ma.getExceptionTableLength(); j++) {
				//TODO 这里是exceptionTable，所以这里的解析是错误的   Later
				ExceptionTable exceptionTable = new ExceptionTable();
				exceptionTable.setStartPc(Bytes.u2(readByte(2)));
				exceptionTable.setEndPc(Bytes.u2(readByte(2)));
				exceptionTable.setHandlerPc(Bytes.u2(readByte(2)));
				exceptionTable.setCatchType(Bytes.u2(readByte(2)));
				ma.getExceptionTable().add(exceptionTable);
			}
			//attribute 
			ma.setAttributeCount(Bytes.u2(readByte(2)));
			for (int j = 0; j < ma.getAttributeCount(); j++) {
				int innerattributeNameIndex = Bytes.u2(readByte(2));
				ma.getArrribute().add(parseAttribute(innerattributeNameIndex, classInfo));
			}

			return ma;
		} else if ("LineNumberTable".equals(type)) {
			LineNumberTaleAttribue lineTable = new LineNumberTaleAttribue();
			lineTable.setAttributeNameIndex(attributeNameIndex);
			lineTable.setAttributeLength(Bytes.u4(readByte(4)));
			lineTable.setLineNumberTableLength(Bytes.u2(readByte(2)));
			for (int j = 0; j < lineTable.getLineNumberTableLength(); j++) {
				lineTable.getInfos().add(
						new LineNumberTableInfo(Bytes.u2(readByte(2)), Bytes
								.u2(readByte(2))));
			}
			return lineTable;
		} else if ("LocalVariableTable".equals(type)) {

			LocalVariableTableAttribute localVariableTable = new LocalVariableTableAttribute();
			localVariableTable.setAttributeNameIndex(attributeNameIndex);
			localVariableTable.setAttributeLength(Bytes.u4(readByte(4)));
			localVariableTable.setLocalVariableTableLength(Bytes
					.u2(readByte(2)));
			for (int j = 0; j < localVariableTable
					.getLocalVariableTableLength(); j++) {
				LocalVariableInfo localVariableInfo = new LocalVariableInfo();
				localVariableInfo.setStartPc(Bytes.u2(readByte(2)));
				localVariableInfo.setLength(Bytes.u2(readByte(2)));
				localVariableInfo.setNameIndex(Bytes.u2(readByte(2)));
				localVariableInfo.setDescriptorIndex(Bytes.u2(readByte(2)));
				localVariableInfo.setIndex(Bytes.u2(readByte(2)));
				localVariableTable.getInfos().add(localVariableInfo);
			}
			return localVariableTable;
		}else if("Exceptions".equals(type)){
			//处理exceptions属性
			ExceptionAttribute exceptions = new ExceptionAttribute();
			exceptions.setAttribyteNameIndex(attributeNameIndex);
			exceptions.setAttributeLength(Bytes.u4(readByte(4)));
			exceptions.setNumberOfExceptions(Bytes.u2(readByte(2)));
			for (int k = 0; k < exceptions.getNumberOfExceptions(); k++) {
				ExceptionIndexTable exceptionIndexTable = new ExceptionIndexTable();
				exceptionIndexTable.setIndex(Bytes.u2(readByte(2)));
				exceptions.getExceptionIndexTables().add(
						exceptionIndexTable);
			}
			return exceptions;
		}else if("SourceFile".equals(type)){
			SourceFileAttribute sourceFileAttribute = new SourceFileAttribute();
			sourceFileAttribute.setAttributeNameIndex(attributeNameIndex);
			sourceFileAttribute.setAttributeLength(Bytes.u4(readByte(4)));
			sourceFileAttribute.setSourceFileIndex(Bytes.u2(readByte(2)));
			return sourceFileAttribute;
		}else if("StackMapTable".equals(type)){
			//TODO 
			StackMapTable stackMapTable = new StackMapTable();
			stackMapTable.setAttributeNameIndex(attributeNameIndex);
			stackMapTable.setAttributeLength(Bytes.u4(readByte(4)));
			stackMapTable.setNumberOfEntry(Bytes.u2(readByte(2)));
			for(int i=0;i<stackMapTable.getNumberOfEntry();i++){
				StackMapFrame frame = new StackMapFrame();
				
				
				
				stackMapTable.getFrames().add(frame);
			}
			return stackMapTable;
		}

		return null;
	}
	private void parseConstPool(ClassInfo classInfo) {
		//System.out.println(classInfo.getConstPoolSize());
		for(int i=0;i<classInfo.getConstPoolSize()-1;i++){
			parseConst(classInfo);
			//
			ConstPoolType type  = classInfo.getConstPools().get(classInfo.getConstPools().size()-1);
			if(type!=null && (type.getClass()==ConstDoubleInfo.class || type.getClass()==ConstLongInfo.class)){
				classInfo.getConstPools().add(new LargeNumberContinue());
				i++;
			}
		}
		
	}

	
	private void parseConst(ClassInfo classInfo) {
		short tag = Bytes.u1(readByte(1)[0]);
		//System.out.println(tag);
		ConstPoolType type = null;
		switch (tag) {
		case 1://const utf8 info
			int len = Bytes.getShort(readByte(2), false);
			type = new ConstUtf8Info(Bytes.byte2String(readByte(len)));
			break;
		case 3://int
			type = new ConstIntegerInfo(Bytes.getShort(readByte(4), false));
			break;
		case 4://float
			type= new ConstFloatInfo(Bytes.getFloat(readByte(4)));
			readByte(4);
			break;
		case 5://long
			type= new ConstLongInfo(Bytes.byte2Long(readByte(8)));
			break;
		case 6://double 
			type= new ConstDoubleInfo(Bytes.getDouble(readByte(8)));
			break;
		case 7://class
			type = new ConstsClassInfo(Bytes.u2(readByte(2)));
			break;
		case 8://string index
			type = new ConstStringInfo(Bytes.u2(readByte(2)));
			break;
		case 9:// fieldref
			type = new ConstFieldRefInfo(Bytes.u2(readByte(2)),Bytes.u2(readByte(2)));
			break;
		case 10://methodref
			type = new ConstMethodRefInfo(Bytes.u2(readByte(2)),Bytes.u2(readByte(2)));
			break;
		case 11://interface ref
			type = new ConstInterfaceMethodRefInfo(Bytes.u2(readByte(2)),Bytes.u2(readByte(2)));
			break;
		case 12://name and type info
			type = new ConstNameAndTypeInfo(Bytes.u2(readByte(2)),Bytes.u2(readByte(2)));
			break;
		case 15://method handle
			type = new ConstMethodHandleInfo(Bytes.u1(readByte(1)[0]),Bytes.u2(readByte(2)));
			break;
		case 16://method type info
			type = new ConstMethodTypeInfo(Bytes.u2(readByte(2)));
			break;
		case 18:// invoke dynamic info
			type =  new ConstInvokeDynamicInfo(Bytes.u2(readByte(2)),Bytes.u2(readByte(2)));
			break;
		}
		classInfo.getConstPools().add(type);
		
	}
	public byte [] readByte(int len){
		byte [] b = new byte[len];
		System.arraycopy(bytes, index, b, 0, len);
		index+=len;
		return b;
	}
}
